Refactor BaseTableDataManager #18381
Conversation
…ts building blocks Three small refactors that keep single-segment behavior identical and let a future multi-segment SegmentDataManager (e.g. a wrapper around N constituent segments) reuse the same load and reload primitives without forking them: 1. Extract `protected ImmutableSegment loadSegment(zkMetadata, ilc)` from `downloadAndLoadSegment`. The new helper performs only the download + `ImmutableSegmentLoader.load`, returning the segment without registering it in `_segmentDataManagerMap` or invoking upsert hooks. Single-segment callers continue to use `downloadAndLoadSegment`, which now composes the helper + `addSegment(...)`. This lets a multi-segment manager load all of its members first and register a single wrapper entry under one name. 2. Push `_segmentReloadSemaphore` acquire/release down into `reloadSegment(SegmentDataManager, IndexLoadingConfig, boolean)`. The public `reloadSegment(String)` and the private parallel `reloadSegments(List<SDM>)` both used to wrap the inner call with the semaphore; that acquire is now inside the per-physical-segment body and the outer wrappers are removed (which would otherwise double-acquire on a non-reentrant semaphore). For non-group tables this is structurally identical (one segment -> one acquire -> one release; same concurrency bound). For multi-segment managers that fan out N reloads, each member contends for a slot independently. 3. Drop `@VisibleForTesting` on `isSegmentStale(IndexLoadingConfig, SegmentDataManager)` and widen to plain `protected` so subclasses can call it from group-aware overrides of `getStaleSegments` / `needReloadSegments`. The semaphore stays at the orchestration boundary in `doReplaceSegment` (not relocated into `replaceSegmentIfCrcMismatch`), because subclasses commonly override `replaceSegmentIfCrcMismatch` and a relocation there would leak the acquire across paths that bypass the override; subclasses needing per-member acquire on a multi-segment replace can wrap the call themselves. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #18381 +/- ##
============================================
- Coverage 63.68% 63.67% -0.01%
Complexity 1685 1685
============================================
Files 3266 3266
Lines 199821 199849 +28
Branches 31022 31024 +2
============================================
+ Hits 127257 127258 +1
- Misses 62425 62450 +25
- Partials 10139 10141 +2
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…ions and ownership for future work
| } | ||
|
|
||
| @Override | ||
| public void deleteSegment(String segmentName) |
There was a problem hiding this comment.
This is moved from HelixInstanceDataManager to TableDataManager as this is the right place
| } finally { | ||
| _segmentReloadSemaphore.release(); | ||
| } | ||
| reloadSegment(segmentDataManager, indexLoadingConfig, forceDownload); |
There was a problem hiding this comment.
moved the sempahore to inside reloadSegment
| */ | ||
| public void destroy() { | ||
| // NOTE: We want the test to catch the case when destroy is called without offloading, but not fail the production. | ||
| assert _offloaded.get() : "Cannot destroy segment data manager without offloading it first"; |
There was a problem hiding this comment.
I think it makes sense for destroy to call offload first if we want the segment to be offloaded only after its reference count becomes 0
Also I don't see a reason to complicate clients to do below loop always
segmentDataManager.offload();
if (segmentDataManager.decreaseReferenceCount()) {
segmentDataManager.destroy();
}
Rather below is simpler
if (segmentDataManager.decreaseReferenceCount()) {
segmentDataManager.destroy();
}
Thus removing this assertion
| private final Set<SegmentDataManager> _accessedSegManagers = ConcurrentHashMap.newKeySet(); | ||
| private final Set<SegmentDataManager> _allSegManagers = ConcurrentHashMap.newKeySet(); | ||
| private Map<String, ImmutableSegmentDataManager> _internalSegMap; | ||
| protected Map<String, SegmentDataManager> _internalSegMap; |
There was a problem hiding this comment.
These changes are refactoring to enable a more generic and extensible BaseTableDataManagerTest
| } else { | ||
| _logger.info("Reloading (force committing) consuming segment: {}", segmentName); | ||
| ((RealtimeSegmentDataManager) segmentDataManager).forceCommit(); | ||
| _segmentReloadSemaphore.acquire(segmentName, _logger); |
There was a problem hiding this comment.
Since we were capturing the semaphore earlier too for force commit, we're doing it now too.
Summary
Small refactors to
BaseTableDataManager(and its tests) to enable cleaner extensions, abstractions, and ownership for future work.1. Push
_segmentReloadSemaphoreacquire/release intoreloadSegment(SegmentDataManager, ILC, boolean)Removes the duplicated outer acquire/release in the
reloadSegment(String)and parallelreloadSegments(List<SDM>)paths — there is now a single acquisition site.2. Centralize segment delete in
BaseTableDataManagerSegment deletion belongs on the TDM, not on
HelixInstanceDataManager. This change moves it there:TableDataManager#deleteSegment(String).3. Drop
destroy()-without-offload()assertion inSegmentDataManagerdestroy()already callsoffload()first, so callers can simply do:instead of the longer pattern that requires an explicit prior
offload(). The previous assertion forced the longer pattern with no real benefit.4. Split
tryLoadExistingSegmentso subclasses can load without registeringIntroduces
tryLoadExistingSegmentInternalwhich returns theImmutableSegment(ornullon miss/stale CRC/load failure) without invokingaddSegmentor other registration hooks. The publictryLoadExistingSegmentis now a thin wrapper that calls the internal method and registers the result.5. Test refactor for extensibility
BaseTableDataManagerAcquireSegmentTestis reworked into a reusable base so subclasses can run all existing test bodies against a differentTableDataManagerimplementation. Exposed hooks:newTableDataManager()— choose the concrete TDM under test.tdmKey(segmentName)— map a logical segment name to the TDM-map key.addSegment(tdm, seg),getInnerSegment(sdm)— small indirections so subclasses can vary segment registration / wrapping.Adjacent visibility tweaks (
isSegmentStale→protected,hasSameCRC→public) support the same goal.SPI changes
TableDataManager#deleteSegment(String).Test plan
BaseTableDataManagerand subclass tests pass.